home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ADA Programming Guide
/
ADA Programming Guide.iso
/
ada_gnu
/
adainc
/
s-tasren.adb
< prev
next >
Wrap
Text File
|
1996-01-30
|
46KB
|
1,326 lines
-----------------------------------------------------------------------------
-- --
-- GNU ADA RUNTIME LIBRARY (GNARL) COMPONENTS --
-- --
-- S Y S T E M . T A S K I N G . R E N D E Z V O U S --
-- --
-- B o d y --
-- --
-- $Revision: 1.24 $ --
-- --
-- Copyright (c) 1991,1992,1993,1994, FSU, All Rights Reserved --
-- --
-- GNARL is free software; you can redistribute it and/or modify it under --
-- terms of the GNU Library General Public License as published by the --
-- Free Software Foundation; either version 2, or (at your option) any --
-- later version. GNARL is distributed in the hope that it will be use- --
-- ful, but but WITHOUT ANY WARRANTY; without even the implied warranty of --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Gen- --
-- eral Library Public License for more details. You should have received --
-- a copy of the GNU Library General Public License along with GNARL; see --
-- file COPYING.LIB. If not, write to the Free Software Foundation, 675 --
-- Mass Ave, Cambridge, MA 02139, USA. --
-- --
------------------------------------------------------------------------------
with System.Task_Primitives; use System.Task_Primitives;
with System.Tasking.Abortion;
-- Used for, Abortion.Defer_Abortion,
-- Abortion.Undefer_Abortion,
-- Abortion.Change_Base_Priority
with System.Tasking.Queuing; use System.Tasking.Queuing;
-- Used for, Queuing.Enqueue,
-- Queuing.Dequeue,
-- Queuing.Dequeue_Head,
-- Queuing.Count_Waiting,
-- Queuing.Select_Task_Entry_Call
with System.Error_Reporting;
-- Used for, Error_Reporting.Assert
with System.Tasking.Utilities;
-- Used for, Utilities.ATCB_Ptr,
-- Utilities.ATCB_To_ID,
-- Utilities.ID_To_ATCB,
-- Utilities.Null_PO;
-- Utilities."<",
-- Utilities.">=",
-- Utilities."=",
-- Utilities.Task_Stage
-- Utilities.Accepting_State
-- Utilities.Vulnerable_Complete_Activation
-- Utilities.Abort_To_Level
-- Utilities.Reset_Priority
-- Utilities.Terminate_Alternative
with System.Compiler_Exceptions;
-- Used for, Compiler_Exceptions."="
-- Exception_ID
-- Null_Exception
-- Tasking_Error_ID
with Unchecked_Conversion;
package body System.Tasking.Rendezvous is
function ID_To_ATCB (ID : Task_ID) return Utilities.ATCB_Ptr
renames Tasking.Utilities.ID_To_ATCB;
function ATCB_To_ID (Ptr : Utilities.ATCB_Ptr) return Task_ID
renames Utilities.ATCB_To_ID;
procedure Assert (B : Boolean; M : String)
renames Error_Reporting.Assert;
procedure Defer_Abortion
renames Abortion.Defer_Abortion;
procedure Undefer_Abortion renames
Abortion.Undefer_Abortion;
-- Following should be replaced by use type ???
function "<" (L, R : Utilities.Task_Stage) return Boolean
renames Utilities."<";
function ">=" (L, R : Utilities.Task_Stage) return Boolean
renames Utilities.">=";
function "=" (L, R : Utilities.Accepting_State) return Boolean
renames Utilities."=";
function "=" (L, R : Compiler_Exceptions.Exception_ID)
return Boolean renames Compiler_Exceptions."=";
function Address_To_Protection_Access is new
Unchecked_Conversion (System.Address, Protection_Access);
function Protection_Access_To_Address is new
Unchecked_Conversion (Protection_Access, System.Address);
type Select_Treatment is (
Accept_Alternative_Selected,
Accept_Alternative_Completed,
Else_Selected,
Terminate_Selected,
Accept_Alternative_Open,
No_Alternative_Open);
Default_Treatment : constant array (Select_Modes) of Select_Treatment :=
(Simple_Mode => No_Alternative_Open,
Else_Mode => Else_Selected,
Terminate_Mode => Terminate_Selected);
procedure Vulnerable_Cancel_Task_Entry_Call
(Call : Entry_Call_Link;
Cancel_Was_Successful : out Boolean);
-- This procedure is used to cancel a task entry call from
-- within the runtime (including from the interface procedure
-- Cancel_Protected_Entry_Call). It assumes that abortion is
-- deferred, and stores rather than raises any required exceptions.
-----------------------
-- Local Subprograms --
-----------------------
procedure Boost_Priority
(Call : Entry_Call_Link;
Acceptor : Utilities.ATCB_Ptr);
pragma Inline (Boost_Priority);
procedure Test_Call
(Entry_Call : in out Entry_Call_Link;
Rendezvous_Completed : out Boolean);
-- Test if a rendezvous can be made right away. Returns True if the
-- rendezvous has occurred (and finished).
-- Problem: Try not to call this when the acceptor is not accepting.
-- What does problem mean??? advice??? why??? absolute rule???
function Test_Selective_Wait
(Acceptor : Utilities.ATCB_Ptr;
Open_Accepts : Accept_List_Access;
Select_Mode : Select_Modes)
return Select_Treatment;
pragma Inline (Test_Selective_Wait);
-- Test if there is a call waiting on any entry, and whether any selects
-- are open. Set Acceptor.Chosen_Index to selected alternative if an
-- accept alternative can be selected.
procedure Universal_Complete_Rendezvous
(Ex : Compiler_Exceptions.Exception_ID);
pragma Inline (Universal_Complete_Rendezvous);
-- Called by acceptor to wake up caller and optionally propagate exception
procedure Adjust_For_Terminate_Alternative_Call (Acceptor : Task_ID);
-- Called by caller to wake up the acceptor if it is waiting on
-- terminate_alternative.
--------------------
-- Boost_Priority --
--------------------
procedure Boost_Priority
(Call : Entry_Call_Link;
Acceptor : Utilities.ATCB_Ptr)
is
Caller : Utilities.ATCB_Ptr := ID_To_ATCB (Call.Self);
begin
if Get_Priority (Caller.LL_TCB'Access) >
Get_Priority (Acceptor.LL_TCB'Access)
then
Call.Acceptor_Prev_Priority := Acceptor.Current_Priority;
Acceptor.Current_Priority := Caller.Current_Priority;
Set_Priority (Acceptor.LL_TCB'Access, Acceptor.Current_Priority);
else
Call.Acceptor_Prev_Priority := Priority_Not_Boosted;
end if;
end Boost_Priority;
---------------
-- Test_Call --
---------------
procedure Test_Call
(Entry_Call : in out Entry_Call_Link;
Rendezvous_Completed : out Boolean)
is
Temp_Entry : Entry_Index;
TAS_Result : Boolean;
Acceptor_ID : Task_ID;
Acceptor : Utilities.ATCB_Ptr;
Caller : Utilities.ATCB_Ptr := ID_To_ATCB (Entry_Call.Self);
Error : Boolean;
begin
Acceptor := ID_To_ATCB (Entry_Call.Called_Task);
if Acceptor.Accepting = Utilities.Trivial_Accept then
Temp_Entry := Entry_Index (Acceptor.Open_Accepts (1).S);
-- Case of rendezvous accepted
if Entry_Call.E = Temp_Entry then
Acceptor.Accepting := Utilities.Not_Accepting;
Entry_Call.Acceptor_Prev_Call := Acceptor.Call;
Acceptor.Call := Entry_Call;
Entry_Call.Done := True;
Rendezvous_Completed := True;
Cond_Signal (Acceptor.Cond); -- Inefficient ???
-- Case of wait for acceptor
else
Rendezvous_Completed := False;
end if;
elsif Acceptor.Accepting = Utilities.Not_Accepting then
if Callable (ATCB_To_ID (Acceptor)) then
Rendezvous_Completed := False;
-- Wait for acceptor
else
if Entry_Call.Mode /= Asynchronous_Call then
Caller.ATC_Nesting_Level := Caller.ATC_Nesting_Level - 1;
end if;
Unlock (Acceptor.L);
Undefer_Abortion;
raise Tasking_Error;
end if;
else
-- Try to do immediate rendezvous
for J in Acceptor.Open_Accepts'Range loop
Temp_Entry := Entry_Index (Acceptor.Open_Accepts (J).S);
if Entry_Call.E = Temp_Entry then -- do rendezvous
Test_And_Set (Entry_Call.Call_Claimed'Address, TAS_Result);
if not TAS_Result then
-- This task has been aborted
Unlock (Acceptor.L);
Write_Lock (Caller.L, Error);
Caller.Suspended_Abortably := True;
loop
if Caller.Pending_Action then
if Caller.Pending_Priority_Change then
Abortion.Change_Base_Priority (Caller);
end if;
exit when
Caller.Pending_ATC_Level < Caller.ATC_Nesting_Level;
Caller.Pending_Action := False;
end if;
Cond_Wait (Caller.Rend_Cond, Caller.L);
end loop;
Caller.Suspended_Abortably := False;
Unlock (Caller.L);
Write_Lock (Acceptor.L, Error);
end if;
Acceptor.Accepting := Utilities.Not_Accepting;
if Acceptor.Open_Accepts (J).Null_Body then
Entry_Call.Done := True;
Acceptor.Chosen_Index := J;
Cond_Signal (Acceptor.Cond);
else
Entry_Call.Acceptor_Prev_Call := Acceptor.Call;
Acceptor.Call := Entry_Call;
Acceptor.Chosen_Index := J;
Boost_Priority (Entry_Call, Acceptor);
Cond_Signal (Acceptor.Cond);
-- This needs to be protected by the caller's mutex, not
-- the acceptor's. Otherwise, there is a risk of loosing a
-- signal. This is dumb code, and probably could be
-- fixed to some extent by getting rid of Test_Call. ???
Unlock (Acceptor.L);
Write_Lock (Caller.L, Error);
while not Entry_Call.Done loop
Cond_Wait (Caller.Rend_Cond, Caller.L);
end loop;
Unlock (Caller.L);
Write_Lock (Acceptor.L, Error);
end if;
Rendezvous_Completed := True;
return;
end if;
end loop;
Rendezvous_Completed := False;
end if;
end Test_Call;
---------------------------------------
-- Vulnerable_Cancel_Task_Entry_Call --
---------------------------------------
procedure Vulnerable_Cancel_Task_Entry_Call
(Call : Entry_Call_Link;
Cancel_Was_Successful : out Boolean)
is
TAS_Result : Boolean;
Caller : Utilities.ATCB_Ptr := ID_To_ATCB (Call.Self);
Acceptor : Utilities.ATCB_Ptr := ID_To_ATCB (Call.Called_Task);
Error : Boolean;
begin
Cancel_Was_Successful := False;
Test_And_Set (Call.Call_Claimed'Address, TAS_Result);
if TAS_Result then
if not Call.Done then
-- We should be able to check this flag at this point; we have
-- claimed the call, so no one will be able to service this call,
-- so no one else should be able to change the Call.Done flag.
Write_Lock (Acceptor.L, Error);
if Onqueue (Call) then
Dequeue (
Acceptor.Entry_Queues (Task_Entry_Index (Call.E)),
Call);
end if;
Unlock (Acceptor.L);
Cancel_Was_Successful := True;
-- Note: this will indicate failure to cancel if the acceptor has
-- canceled the call due to completion. Of course, we are going
-- to raise an exception in that case, so I think that this is
-- OK; the flag retuned to the application code should never be
-- used.
end if;
else
Write_Lock (Caller.L, Error);
while not Call.Done loop
Cond_Wait (Caller.Rend_Cond, Caller.L);
end loop;
Unlock (Caller.L);
end if;
Caller.ATC_Nesting_Level := Caller.ATC_Nesting_Level - 1;
Write_Lock (Caller.L, Error);
if Caller.Pending_ATC_Level = Caller.ATC_Nesting_Level then
Caller.Pending_ATC_Level := ATC_Level_Infinity;
Caller.Aborting := False;
end if;
Unlock (Caller.L);
-- If we have reached the desired ATC nesting level, reset the
-- requested level to effective infinity, to allow further calls.
Caller.Exception_To_Raise := Call.Exception_To_Raise;
end Vulnerable_Cancel_Task_Entry_Call;
-------------------------------------------
-- Adjust_For_Terminate_Alternative_Call --
-------------------------------------------
procedure Adjust_For_Terminate_Alternative_Call (Acceptor : Task_ID) is
Acceptor_ATCB : Utilities.ATCB_Ptr := ID_To_ATCB (Acceptor);
P : Utilities.ATCB_Ptr;
Error : boolean;
begin
Write_Lock (Acceptor_ATCB.L, Error);
if Acceptor_ATCB.Terminate_Alternative then
Acceptor_ATCB.Stage := Utilities.Active;
Acceptor_ATCB.Awake_Count := Acceptor_ATCB.Awake_Count + 1;
-- At this point, T.Awake_Count and P.Awaited_Dependent_Count could
-- be out of synchronization. However, we know that
-- P.Awaited_Dependent_Count cannot be zero, and cannot go to zero,
-- since some other dependent must have just called us. There should
-- therefore be no danger of the parent terminating before we
-- increment P.Awaited_Dependent_Count below.
if Acceptor_ATCB.Awake_Count = 1 then
Unlock (Acceptor_ATCB.L);
if Acceptor_ATCB.Pending_ATC_Level <
Acceptor_ATCB.ATC_Nesting_Level then
Abortion.Undefer_Abortion;
Error_Reporting.Assert
(False, "Continuing after being aborted!");
end if;
P := Acceptor_ATCB.Parent;
Write_Lock (P.L, Error);
if P.Awake_Count /= 0 then
P.Awake_Count := P.Awake_Count + 1;
else
Unlock (P.L);
Utilities.Abort_To_Level (Acceptor, 0);
Abortion.Undefer_Abortion;
Error_Reporting.Assert
(False, "Continuing after being aborted!");
end if;
-- Conservative checks which should only matter when an interrupt
-- entry was chosen. In this case, the current task completes if
-- the parent has already been signaled that all children have
-- terminated.
if Acceptor_ATCB.Master_of_Task = P.Master_Within then
if P.Awaited_Dependent_Count /= 0 then
P.Awaited_Dependent_Count := P.Awaited_Dependent_Count + 1;
elsif P.Stage = Utilities.Await_Dependents then
Unlock (P.L);
Utilities.Abort_To_Level (Acceptor, 0);
Abortion.Undefer_Abortion;
Error_Reporting.Assert (
False, "Continuing after being aborted!");
end if;
end if;
Unlock (P.L);
else
Unlock (Acceptor_ATCB.L);
if Acceptor_ATCB.Pending_ATC_Level <
Acceptor_ATCB.ATC_Nesting_Level then
Abortion.Undefer_Abortion;
Error_Reporting.Assert
(False, "Continuing after being aborted!");
end if;
end if;
Write_Lock (Acceptor_ATCB.L, Error);
Acceptor_ATCB.Terminate_Alternative := false;
-- Need to set this flag off in order not to make subsequent calls
-- to be treated to calls to Select With Terminate Alternative.
end if;
Unlock (Acceptor_ATCB.L);
end Adjust_For_Terminate_Alternative_Call;
-----------------
-- Call_Simple --
-----------------
procedure Call_Simple
(Acceptor : Task_ID;
E : Task_Entry_Index;
Uninterpreted_Data : System.Address)
is
Caller : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Acceptor_ATCB : Utilities.ATCB_Ptr := ID_To_ATCB (Acceptor);
Rendezvous_Completed : Boolean;
Level : ATC_Level;
Entry_Call : Entry_Call_Link;
Cancel_Was_Successful : Boolean;
Error : Boolean;
begin
Defer_Abortion;
Caller.ATC_Nesting_Level := Caller.ATC_Nesting_Level + 1;
Level := Caller.ATC_Nesting_Level;
Entry_Call := Caller.Entry_Calls (Level)'Access;
Entry_Call.Next := null;
Entry_Call.Call_Claimed := False;
Entry_Call.Mode := Simple_Call;
Entry_Call.Abortable := True;
Entry_Call.Done := False;
Entry_Call.E := Entry_Index (E);
Entry_Call.Prio := Caller.Current_Priority;
Entry_Call.Uninterpreted_Data := Uninterpreted_Data;
Entry_Call.Called_Task := Acceptor;
Entry_Call.Exception_To_Raise := Compiler_Exceptions.Null_Exception;
-- if Acceptor_ATCB.Accepting = Utilities.Not_Accepting and then
-- not Callable (Acceptor) then
-- -- Acceptor must have gone beyond the complete stage.
-- -- This situation may occur when the acceptor and caller are
-- -- declared in different context. (ex) Interrupt Handler.
-- -- This is the case where the acceptor is completed before
-- -- the entry call is made. In such a case, just cancel the
-- -- call.
-- Unlock (Acceptor_ATCB.L);
-- Vulnerable_Cancel_Task_Entry_Call
-- (Entry_Call, Cancel_Was_Successful);
-- Undefer_Abortion;
-- return;
-- end if;
-- Note: the caller will undefer abortion on return (see WARNING above)
Adjust_For_Terminate_Alternative_Call (Acceptor);
Write_Lock (Acceptor_ATCB.L, Error);
Test_Call (Entry_Call, Rendezvous_Completed);
if not Rendezvous_Completed then
Enqueue (Acceptor_ATCB.Entry_Queues (E), Entry_Call);
Unlock (Acceptor_ATCB.L);
Write_Lock (Caller.L, Error);
Caller.Suspended_Abortably := True;
while not Entry_Call.Done loop
if Caller.Pending_Action then
if Caller.Pending_Priority_Change then
Abortion.Change_Base_Priority (Caller);
-- requeue call at new priority
Unlock (Caller.L);
Write_Lock (Acceptor_ATCB.L, Error);
if Onqueue (Entry_Call) then -- Dequeued by acceptor?
Dequeue (Acceptor_ATCB.Entry_Queues (E), Entry_Call);
Enqueue (Acceptor_ATCB.Entry_Queues (E), Entry_Call);
end if;
Unlock (Acceptor_ATCB.L);
Write_Lock (Caller.L, Error);
end if;
exit when
Caller.Pending_ATC_Level < Caller.ATC_Nesting_Level and then
not Entry_Call.Call_Claimed;
end if;
Cond_Wait (Caller.Rend_Cond, Caller.L);
end loop;
Caller.Suspended_Abortably := False;
Unlock (Caller.L);
else
Unlock (Acceptor_ATCB.L);
end if;
-- NOTICE:
-- There is a distinction made between asynchronous calls and the rest:
-- The asynchronous calls are always cleaned up by
-- Cancel_Task_Entry_Call, but the others get cleaned up by
-- Task_Entry_Call or its equivalent.
-- Complete no longer does a decrement, so who does if this task is
-- aborted at this point? The decrement should take
-- place before undeferring abortion, and that this should include
-- taking the call off any queue it might be on.
-- Problem: What if it is claimed in the meantime by an acceptor? The
-- test for Call_Claimed in the wait loop is really vulnerable to race
-- conditions on this point. We can't get out of the loop until
-- Call_Claimed is false, but there is nothing to keep it from
-- staying false. By the time we get here, rendezvous could be in
-- progress. The only solution is to claim the call here in order
-- to cancel it. However, what do we do if we loose? Wait again?
-- I think so. I also think that that works: wait until done or
-- aborted; if aborted, attempt to cancel the call; if that fails, wait
-- until the call (now well and truly started) completes, without
-- benefit of Suspended_Abortably.
-- Problem: The acceptor might also claim the call on completion, to
-- cancel it. In that case, it has already awakened us, and won't do it
-- again.
-- I think this is OK. Close_Entries already pretends that the
-- call has been completed, and has already set the exception at that
-- point.
Vulnerable_Cancel_Task_Entry_Call (Entry_Call, Cancel_Was_Successful);
Undefer_Abortion;
Assert (Caller.Pending_ATC_Level >= Caller.ATC_Nesting_Level,
"Continuing after aborting self!");
Utilities.Check_Exception;
end Call_Simple;
----------------------------
-- Cancel_Task_Entry_Call --
----------------------------
procedure Cancel_Task_Entry_Call (Cancelled : out Boolean) is
Caller : Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Call : Entry_Call_Link;
Acceptor : Utilities.ATCB_Ptr;
begin
Assert (Caller.ATC_Nesting_Level > ATC_Level_Base'First,
"Attempt to cancel nonexistant task entry call.");
Call := Caller.Entry_Calls (Caller.ATC_Nesting_Level)'Access;
Assert (Call.Mode = Asynchronous_Call,
"Attempt to perform ATC on a non-asynchronous task entry call");
Assert (Address_To_Protection_Access (Call.Called_PO) =
Utilities.Null_PO,
"Attempt to use Cancel_Task_Entry_Call on protected entry call.");
Acceptor := ID_To_ATCB (Call.Called_Task);
Defer_Abortion;
Vulnerable_Cancel_Task_Entry_Call (Call, Cancelled);
Undefer_Abortion;
Utilities.Check_Exception;
end Cancel_Task_Entry_Call;
------------------------
-- Requeue_Task_Entry --
------------------------
procedure Requeue_Task_Entry
(Acceptor : Task_ID;
E : Task_Entry_Index;
With_Abort : Boolean)
is
Old_Acceptor : Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Entry_Call : Entry_Call_Link;
Acceptor_ATCB : Utilities.ATCB_Ptr := ID_To_ATCB (Acceptor);
Error : Boolean;
begin
Write_Lock (Old_Acceptor.L, Error);
Entry_Call := Old_Acceptor.Call;
Old_Acceptor.Call := null;
Unlock (Old_Acceptor.L);
Entry_Call.Abortable := With_Abort;
Entry_Call.E := Entry_Index (E);
if With_Abort then
Entry_Call.Call_Claimed := False;
end if;
Write_Lock (Acceptor_ATCB.L, Error);
Enqueue (Acceptor_ATCB.Entry_Queues (E), Entry_Call);
Unlock (Acceptor_ATCB.L);
end Requeue_Task_Entry;
-------------------------------------
-- Requeue_Protected_To_Task_Entry --
-------------------------------------
procedure Requeue_Protected_To_Task_Entry
(Object : Protection_Access;
Acceptor : Task_ID;
E : Task_Entry_Index;
With_Abort : Boolean)
is
Old_Acceptor : Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Entry_Call : Entry_Call_Link;
Acceptor_ATCB : Utilities.ATCB_Ptr := ID_To_ATCB (Acceptor);
Error : Boolean;
begin
Object.Call_In_Progress.Abortable := With_Abort;
Object.Call_In_Progress.E := Entry_Index (E);
if With_Abort then
Object.Call_In_Progress.Call_Claimed := False;
end if;
Write_Lock (Acceptor_ATCB.L, Error);
Enqueue (Acceptor_ATCB.Entry_Queues (E), Object.Call_In_Progress);
Unlock (Acceptor_ATCB.L);
end Requeue_Protected_To_Task_Entry;
---------------------
-- Task_Entry_Call --
---------------------
procedure Task_Entry_Call
(Acceptor : Task_ID;
E : Task_Entry_Index;
Uninterpreted_Data : System.Address;
Mode : Call_Modes;
Rendezvous_Successful : out Boolean)
is
Caller : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Acceptor_ATCB : Utilities.ATCB_Ptr := ID_To_ATCB (Acceptor);
Rendezvous_Completed : Boolean;
Entry_Call : Entry_Call_Link;
Cancel_Was_Successful : Boolean;
Error : Boolean;
begin
-- Simple call
if Mode = Simple_Call then
Call_Simple (Acceptor, E, Uninterpreted_Data);
Rendezvous_Successful := True;
return;
-- Conditional call
elsif Mode = Conditional_Call then
Defer_Abortion;
Caller.ATC_Nesting_Level := Caller.ATC_Nesting_Level + 1;
Entry_Call := Caller.Entry_Calls (Caller.ATC_Nesting_Level)'Access;
Entry_Call.Next := null;
Entry_Call.Call_Claimed := False;
Entry_Call.Mode := Mode;
Entry_Call.Abortable := True;
Entry_Call.Done := False;
Entry_Call.E := Entry_Index (E);
Entry_Call.Prio := Caller.Current_Priority;
Entry_Call.Uninterpreted_Data := Uninterpreted_Data;
Entry_Call.Called_Task := Acceptor;
Entry_Call.Exception_To_Raise := Compiler_Exceptions.Null_Exception;
Adjust_For_Terminate_Alternative_Call (Acceptor);
Write_Lock (Acceptor_ATCB.L, Error);
Test_Call (Entry_Call, Rendezvous_Completed);
Unlock (Acceptor_ATCB.L);
Vulnerable_Cancel_Task_Entry_Call (Entry_Call, Cancel_Was_Successful);
Undefer_Abortion;
Assert (Caller.Pending_ATC_Level >= Caller.ATC_Nesting_Level,
"Continuing after aborting self!");
Utilities.Check_Exception;
Rendezvous_Successful := Entry_Call.Done;
return;
-- Asynchronous call
else
Defer_Abortion;
Caller.ATC_Nesting_Level := Caller.ATC_Nesting_Level + 1;
Entry_Call := Caller.Entry_Calls (Caller.ATC_Nesting_Level)'Access;
Entry_Call.Next := null;
Entry_Call.Call_Claimed := False;
Entry_Call.Mode := Mode;
Entry_Call.Abortable := True;
Entry_Call.Done := False;
Entry_Call.E := Entry_Index (E);
Entry_Call.Prio := Caller.Current_Priority;
Entry_Call.Uninterpreted_Data := Uninterpreted_Data;
Entry_Call.Called_Task := Acceptor;
Entry_Call.Called_PO :=
Protection_Access_To_Address (Utilities.Null_PO);
Entry_Call.Exception_To_Raise := Compiler_Exceptions.Null_Exception;
Adjust_For_Terminate_Alternative_Call (Acceptor);
Write_Lock (Acceptor_ATCB.L, Error);
Test_Call (Entry_Call, Rendezvous_Completed);
if not Rendezvous_Completed then
Enqueue (Acceptor_ATCB.Entry_Queues (E), Entry_Call);
end if;
Unlock (Acceptor_ATCB.L);
Undefer_Abortion;
Rendezvous_Successful := Entry_Call.Done;
-- Amazingly, this seems to be all the work that is needed.
-- Asynchronous calls are set up so that they are always explicitly
-- canceled in in the compiled code. It might be worth considering
-- unifying the various calls, and explitely cancelling all of them.
-- This is not very efficiant, unfortunately. Perhaps this call
-- should unify them, with other calls for optimization? Then who
-- would want to use this call???
end if;
end Task_Entry_Call;
-----------------
-- Accept_Call --
-----------------
procedure Accept_Call
(E : Task_Entry_Index;
Uninterpreted_Data : out System.Address)
is
Acceptor : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Caller : Utilities.ATCB_Ptr := null;
TAS_Result : Boolean;
Open_Accepts : aliased Accept_List (1 .. 1);
Entry_Call : Entry_Call_Link;
Error : Boolean;
begin
Defer_Abortion;
Write_Lock (Acceptor.L, Error);
-- If someone is completing this task, it must be because they plan
-- to abort it. This task should not try to access its pending entry
-- calls or queues in this case, as they are being emptied. Wait for
-- abortion to kill us.
if Acceptor.Stage >= Utilities.Completing then
loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
Unlock (Acceptor.L);
Undefer_Abortion;
Assert (False, "Continuing execution after being aborted.");
end if;
loop
Dequeue_Head (Acceptor.Entry_Queues (E), Entry_Call);
if Entry_Call /= null then
Test_And_Set (Entry_Call.Call_Claimed'Address, TAS_Result);
exit when TAS_Result;
-- TAS_Result = False only when the caller is already aborted or
-- timed out; in that case, go on to the next caller on the queue
else
exit;
end if;
end loop;
if Entry_Call /= null then
Caller := ID_To_ATCB (Entry_Call.Self);
Boost_Priority (Entry_Call, Acceptor);
Entry_Call.Acceptor_Prev_Call := Acceptor.Call;
Acceptor.Call := Entry_Call;
Uninterpreted_Data := Entry_Call.Uninterpreted_Data;
else
-- Wait for a caller
Open_Accepts (1).Null_Body := false;
Open_Accepts (1).S := E;
Acceptor.Open_Accepts := Open_Accepts'Access;
Acceptor.Accepting := Utilities.Simple_Accept;
-- Wait for normal call
Acceptor.Suspended_Abortably := True;
while Acceptor.Accepting /= Utilities.Not_Accepting loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
Acceptor.Suspended_Abortably := False;
if Acceptor.Pending_ATC_Level >= Acceptor.ATC_Nesting_Level then
Caller := ID_To_ATCB (Acceptor.Call.Self);
Uninterpreted_Data :=
Caller.Entry_Calls (Caller.ATC_Nesting_Level).Uninterpreted_Data;
end if;
-- If this task has been aborted, skip the Uninterpreted_Data load
-- (Caller will not be reliable) and fall through to
-- Undefer_Abortion which will allow the task to be killed.
end if;
-- At this point, the call has been claimed, either by the acceptor
-- or by the caller on behalf of the acceptor.
-- Acceptor.Call should already be updated by the Caller
Unlock (Acceptor.L);
Undefer_Abortion;
-- Start rendezvous
end Accept_Call;
--------------------
-- Accept_Trivial --
--------------------
procedure Accept_Trivial (E : Task_Entry_Index) is
Acceptor : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Caller : Utilities.ATCB_Ptr := null;
TAS_Result : Boolean;
Open_Accepts : aliased Accept_List (1 .. 1);
Entry_Call : Entry_Call_Link;
Error : Boolean;
begin
Defer_Abortion;
Write_Lock (Acceptor.L, Error);
-- If someone is completing this task, it must be because they plan
-- to abort it. This task should not try to access its pending entry
-- calls or queues in this case, as they are being emptied. Wait for
-- abortion to kill us.
if Acceptor.Stage >= Utilities.Completing then
loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
Unlock (Acceptor.L);
Undefer_Abortion;
Assert (False, "Continuing execution after being aborted.");
end if;
loop
Dequeue_Head (Acceptor.Entry_Queues (E), Entry_Call);
if Entry_Call = null then
-- Need to wait for call
Open_Accepts (1).Null_Body := False;
Open_Accepts (1).S := E;
Acceptor.Open_Accepts := Open_Accepts'Access;
Acceptor.Accepting := Utilities.Trivial_Accept;
-- Wait for normal entry call
Acceptor.Suspended_Abortably := True;
while Acceptor.Accepting /= Utilities.Not_Accepting loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
if Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level then
Unlock (Acceptor.L);
Undefer_Abortion;
Assert (False, "Continuing after being aborted!");
else
Acceptor.Suspended_Abortably := False;
Entry_Call := Acceptor.Call;
Acceptor.Call := Entry_Call.Acceptor_Prev_Call;
Caller := ID_To_ATCB (Entry_Call.Self);
if Entry_Call.Mode = Asynchronous_Call then
Utilities.Abort_To_Level
(ATCB_To_ID (Caller),
Entry_Call.Level - 1);
end if;
Unlock (Acceptor.L);
end if;
exit;
end if;
Test_And_Set (Entry_Call.Call_Claimed'Address, TAS_Result);
if TAS_Result then
-- Caller is waiting; there is no accept body
Caller := ID_To_ATCB (Entry_Call.Self);
Unlock (Acceptor.L);
Write_Lock (Caller.L, Error);
Entry_Call.Done := True;
-- Done with mutex locked to make sure that signal is not lost.
Unlock (Caller.L);
Entry_Call.Call_Claimed := False;
if Entry_Call.Mode = Asynchronous_Call then
Utilities.Abort_To_Level (
ATCB_To_ID (Caller), Entry_Call.Level - 1);
else
Cond_Signal (Caller.Rend_Cond);
end if;
exit;
end if;
-- TAS_Result = False only when the caller is already aborted or has
-- timed out; in that case, we go on to the next caller on the queue
end loop;
Undefer_Abortion;
end Accept_Trivial;
-----------------------------------
-- Universal_Complete_Rendezvous --
-----------------------------------
procedure Universal_Complete_Rendezvous
(Ex : Compiler_Exceptions.Exception_ID)
is
Acceptor : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Caller : Utilities.ATCB_Ptr;
Call : Entry_Call_Link;
Prev_Priority : Rendezvous_Priority;
Error : Boolean;
begin
Defer_Abortion;
Call := Acceptor.Call;
Acceptor.Call := Call.Acceptor_Prev_Call;
Prev_Priority := Call.Acceptor_Prev_Priority;
Call.Exception_To_Raise := Ex;
Caller := ID_To_ATCB (Call.Self);
Call.Call_Claimed := False;
Write_Lock (Caller.L, Error);
Call.Done := True;
Unlock (Caller.L);
if Call.Mode = Asynchronous_Call then
Utilities.Abort_To_Level (ATCB_To_ID (Caller), Call.Level - 1);
else
Cond_Signal (Caller.Rend_Cond);
end if;
Utilities.Reset_Priority (Prev_Priority, ATCB_To_ID (Acceptor));
Acceptor.Exception_To_Raise := Ex;
-- Save the exception for Complete_Rendezvous.
Undefer_Abortion;
end Universal_Complete_Rendezvous;
-------------------------
-- Complete_Rendezvous --
-------------------------
procedure Complete_Rendezvous is
begin
Universal_Complete_Rendezvous (Compiler_Exceptions.Null_Exception);
end Complete_Rendezvous;
-------------------------------------
-- Exceptional_Complete_Rendezvous --
-------------------------------------
procedure Exceptional_Complete_Rendezvous
(Ex : Compiler_Exceptions.Exception_ID)
is
begin
Universal_Complete_Rendezvous (Ex);
end Exceptional_Complete_Rendezvous;
-------------------------
-- Test_Selective_Wait --
-------------------------
function Test_Selective_Wait
(Acceptor : Utilities.ATCB_Ptr;
Open_Accepts : Accept_List_Access;
Select_Mode : Select_Modes) return Select_Treatment
is
Temp_Entry : Task_Entry_Index;
TAS_Result : Boolean;
Treatment : Select_Treatment;
Entry_Call : Entry_Call_Link;
Caller : Utilities.ATCB_Ptr;
Error : Boolean;
Selection : Select_Index;
begin
Treatment := Default_Treatment (Select_Mode);
Acceptor.Chosen_Index := No_Rendezvous;
Select_Task_Entry_Call (Acceptor, Open_Accepts, Entry_Call, Selection);
if Entry_Call /= null then
if Open_Accepts (Selection).Null_Body then
Caller := ID_To_ATCB (Entry_Call.Self);
Entry_Call.Call_Claimed := False;
Write_Lock (Caller.L, Error);
Entry_Call.Done := True;
Unlock (Caller.L);
if Entry_Call.Mode = Asynchronous_Call then
Utilities.Abort_To_Level (
ATCB_To_ID (Caller),
Entry_Call.Level - 1);
else
Cond_Signal (Caller.Rend_Cond);
end if;
Treatment := Accept_Alternative_Completed;
else
Boost_Priority (Entry_Call, Acceptor);
Entry_Call.Acceptor_Prev_Call := Acceptor.Call;
Acceptor.Call := Entry_Call;
Treatment := Accept_Alternative_Selected;
end if;
Acceptor.Chosen_Index := Selection;
elsif Treatment = No_Alternative_Open then
Treatment := Accept_Alternative_Open;
end if;
-- Do rendezvous
return Treatment;
end Test_Selective_Wait;
--------------------
-- Selective_Wait --
--------------------
procedure Selective_Wait
(Open_Accepts : Accept_List_Access;
Select_Mode : Select_Modes;
Uninterpreted_Data : out System.Address;
Index : out Select_Index)
is
Acceptor : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Treatment : Select_Treatment;
I_Result : Integer;
Error : Boolean;
begin
Defer_Abortion;
Write_Lock (Acceptor.L, Error);
-- If someone is completing this task, it must be because they plan
-- to abort it. This task should not try to access its pending entry
-- calls or queues in this case, as they are being emptied. Wait for
-- abortion to kill us.
if Acceptor.Stage >= Utilities.Completing then
loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
Undefer_Abortion;
Assert (False, "Continuing execution after being aborted.");
end if;
Treatment := Test_Selective_Wait (Acceptor, Open_Accepts, Select_Mode);
case Treatment is
when Accept_Alternative_Selected =>
-- Ready to rendezvous already
Uninterpreted_Data := Acceptor.Call.Uninterpreted_Data;
when Accept_Alternative_Completed =>
-- Rendezvous is over
null;
when Accept_Alternative_Open =>
-- Wait for caller.
Acceptor.Open_Accepts := Open_Accepts;
Acceptor.Accepting := Utilities.Select_Wait;
Acceptor.Suspended_Abortably := True;
while Acceptor.Accepting /= Utilities.Not_Accepting
loop
if Acceptor.Pending_Action then
if Acceptor.Pending_Priority_Change then
Abortion.Change_Base_Priority (Acceptor);
end if;
exit when
Acceptor.Pending_ATC_Level < Acceptor.ATC_Nesting_Level;
Acceptor.Pending_Action := False;
end if;
Cond_Wait (Acceptor.Cond, Acceptor.L);
end loop;
Acceptor.Suspended_Abortably := False;
if Acceptor.Pending_ATC_Level >= Acceptor.ATC_Nesting_Level and then
not Open_Accepts (Acceptor.Chosen_Index).Null_Body then
Uninterpreted_Data := Acceptor.Call.Uninterpreted_Data;
end if;
-- Acceptor.Call should already be updated by the Caller if
-- not aborted.
when Else_Selected =>
Acceptor.Accepting := Utilities.Not_Accepting;
when Terminate_Selected =>
-- Terminate alternative is open
Acceptor.Open_Accepts := Open_Accepts;
Acceptor.Accepting := Utilities.Select_Wait;
-- We need to check if a signal is pending on an open interrupt
-- entry. Otherwise this task would become passive (since terminate
-- alternative is open) and, if none of the siblings are active
-- anymore, the task could not wake up anymore, even though a
-- signal might be pending on an open interrupt entry.
Unlock (Acceptor.L);
Utilities.Terminate_Alternative;
-- Wait for normal entry call or termination
-- consider letting Terminate_Alternative assume mutex L
-- is already locked, and return with it locked, so
-- this code could be simplified???
-- No return here if Acceptor completes, otherwise
-- Acceptor.Call should already be updated by the Caller
Index := Acceptor.Chosen_Index;
if not Open_Accepts (Acceptor.Chosen_Index).Null_Body then
Uninterpreted_Data := Acceptor.Call.Uninterpreted_Data;
end if;
Undefer_Abortion;
return;
when No_Alternative_Open =>
-- Acceptor.Chosen_Index := No_Rendezvous; => Program_Error ???
null;
end case;
-- Caller has been chosen
-- Acceptor.Call should already be updated by the Caller
-- Acceptor.Chosen_Index should either be updated by the Caller
-- or by Test_Selective_Wait
Index := Acceptor.Chosen_Index;
Unlock (Acceptor.L);
Undefer_Abortion;
-- Start rendezvous
end Selective_Wait;
----------------
-- Task_Count --
----------------
function Task_Count (E : Task_Entry_Index) return Natural is
T : constant Utilities.ATCB_Ptr := ID_To_ATCB (Self);
Return_Count : Natural;
Error : Boolean;
begin
Write_Lock (T.L, Error);
Return_Count := Count_Waiting (T.Entry_Queues (E));
Unlock (T.L);
return Return_Count;
end Task_Count;
--------------
-- Callable --
--------------
function Callable (T : Task_ID) return Boolean is
begin
return ID_To_ATCB (T).Stage < Utilities.Complete
and then ID_To_ATCB (T).Pending_ATC_Level > ATC_Level_Base'First;
end Callable;
end System.Tasking.Rendezvous;